package frame.options.simulation;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map.Entry;

import model.algorithms.embedding.EmbeddingAlgorithm;
import model.algorithms.linksMapping.LinksMappingAlgorithm;
import model.algorithms.nodesMapping.Greedy;
import model.algorithms.nodesMapping.NodesMappingAlgorithm;
import model.algorithms.rounding.RoundingAlgorithm;
import model.algorithms.splitting.SplittingAlgorithm;
import model.components.links.link.SdnLink;
import model.components.links.link.VirtualLink;
import model.components.networks.Network;
import model.components.networks.sdn.SdnNetwork;
import model.components.networks.virtual.VirtualNetwork;
import model.components.nodes.node.sdn.SdnNode;
import model.components.nodes.node.virtual.VirtualMachine;
import model.components.nodes.node.virtual.VirtualNode;

public class Simulation
{
	public static final String CANCELED_STATE = "Canceled";
	public static final String DONE_STATE     = "Done";
	public static final String READY_STATE    = "Ready";
	
	private String name;
	public String getName(){ return name; }
	public void setName( String name ){ this.name = name; }
	
	private String state;
	public String getState(){ return state; }
	public void setState( String state ){ this.state = state; }
	
	private List< SdnNetwork > sdnNetworks;
	public List< SdnNetwork > getSdnNetworks(){ return sdnNetworks; }	
	public void setSdnNetworks( List< SdnNetwork > sdnNetworks ){ this.sdnNetworks = sdnNetworks; }
	
	private List< VirtualNetwork > virtualNetworks;
	public List< VirtualNetwork > getVirtualNetworks(){ return virtualNetworks; }
	public void setVirtualNetworks( List< VirtualNetwork > virtualNetworks ){ this.virtualNetworks = virtualNetworks; }
	
	private SplittingAlgorithm splittingAlgorithm;
	public SplittingAlgorithm getSplittingAlgorithm(){ return splittingAlgorithm; }
	public void setSplittingAlgorithm( SplittingAlgorithm splittingAlgorithm ){ this.splittingAlgorithm = splittingAlgorithm; }
	
	private NodesMappingAlgorithm nodesMappingAlgorithm;
	public NodesMappingAlgorithm getNodesMappingAlgorithm(){ return nodesMappingAlgorithm; }
	public void setNodesMappingAlgorithm( NodesMappingAlgorithm nodesMappingAlgorithm ){ this.nodesMappingAlgorithm = nodesMappingAlgorithm; }
	
	private EmbeddingAlgorithm embeddingAlgorithm;
	public EmbeddingAlgorithm getEmbeddingAlgorithm(){ return embeddingAlgorithm; }
	public void setEmbeddingAlgorithm( EmbeddingAlgorithm embeddingAlgorithm ){ this.embeddingAlgorithm = embeddingAlgorithm; }
	
	private RoundingAlgorithm roundingAlgorithm;
	public RoundingAlgorithm getRoundingAlgorithm(){ return roundingAlgorithm; }
	public void setRoundingAlgorithm( RoundingAlgorithm roundingAlgorithm ){ this.roundingAlgorithm = roundingAlgorithm; }
	
	private LinksMappingAlgorithm linksMappingAlgorithm;
	public LinksMappingAlgorithm getLinksMappingAlgorithm(){ return linksMappingAlgorithm; }
	public void setLinksMappingAlgorithm( LinksMappingAlgorithm linksMappingAlgorithm ){ this.linksMappingAlgorithm = linksMappingAlgorithm; }
	
	private int rejectedVirtualNetworksNumber;
	public int getRejectedVirtualNetworksNumber(){ return rejectedVirtualNetworksNumber; }
	public void setRejectedVirtualNetworksNumber( int rejectedVirtualNetworksNumber ){ this.rejectedVirtualNetworksNumber = rejectedVirtualNetworksNumber; }
	
	public Simulation( List< SdnNetwork > sdnNetworks, List< VirtualNetwork > virtualNetworks, SplittingAlgorithm splittingAlgorithm, NodesMappingAlgorithm nodesMappingAlgorithm, EmbeddingAlgorithm embeddingAlgorithm, RoundingAlgorithm roundingAlgorithm, LinksMappingAlgorithm linksMappingAlgorithm )
	{
		this.setSdnNetworks( sdnNetworks );
		this.setVirtualNetworks( virtualNetworks );
		this.setSplittingAlgorithm( splittingAlgorithm );
		this.setNodesMappingAlgorithm( nodesMappingAlgorithm );
		this.setEmbeddingAlgorithm( embeddingAlgorithm );
		this.setRoundingAlgorithm( roundingAlgorithm );
		this.setLinksMappingAlgorithm( linksMappingAlgorithm );
		
		if( this.getSdnNetworks().size() > 1 )
			splittingAlgorithm.setSdnNetworks( sdnNetworks );
		
		if( embeddingAlgorithm == null )
			this.setName( splittingAlgorithm.getName() + "_" + nodesMappingAlgorithm.getName() + "_" + linksMappingAlgorithm.getName() );
		else
			this.setName( splittingAlgorithm.getName() + "_" + embeddingAlgorithm.getName() + "_" + roundingAlgorithm.getName() + "_" + linksMappingAlgorithm.getName() );
		
		this.setState( READY_STATE );
	}
	
	public int run( int timeInstant )
	{
		this.networksReleasing( this.completedVirtualNetworks( timeInstant ) );
		List< VirtualNetwork > startingVirtualNetworks = this.startingVirtualNetworks( timeInstant );
		
		for( VirtualNetwork startingVirtualNetwork: startingVirtualNetworks )
		{
			LinkedHashMap< String, LinkedHashMap< VirtualNode, SdnNode > > nodesMappings = new LinkedHashMap< String, LinkedHashMap< VirtualNode, SdnNode > >();
			LinkedHashMap< String, int[][][][] > linksMappings = new LinkedHashMap< String, int[][][][] >();
			
			List< VirtualNetwork > virtualSubnetworks = new ArrayList< VirtualNetwork >();
			
			if( this.getSdnNetworks().size() == 1 )
			{	
				int i = 0;
				
				for( VirtualNode virtualNode: startingVirtualNetwork.getVirtualNodes() )
					virtualNode.setNumber( i++ );
				
				i = 0;
				
				for( VirtualLink virtualLink: startingVirtualNetwork.getVirtualLinks() )
					virtualLink.setNumber( i++ );
				
				startingVirtualNetwork.setSdnNetworkName( this.getSdnNetworks().get( 0 ).getName() );
				
				virtualSubnetworks.add( startingVirtualNetwork );
			}
			else
				virtualSubnetworks = this.getSplittingAlgorithm().run( startingVirtualNetwork );
			
			startingVirtualNetwork.setVirtualSubnetworks( virtualSubnetworks );
			
			if( virtualSubnetworks == null )
			{
				System.out.println( "SplittingAlgorithm KO" );
				startingVirtualNetwork.setDenied( true );
			}
			else
			{
				for( SdnNetwork sdnNetwork: this.getSdnNetworks() )
				{
					if( startingVirtualNetwork.isDenied() )
						break;
					
					for( VirtualNetwork virtualSubnetwork: virtualSubnetworks )
					{
						if( virtualSubnetwork.getSdnNetworkName().equals( sdnNetwork.getName() ) )
							if( virtualSubnetwork.getLinks().size() == 0 || virtualSubnetwork.getNodes().size() == 1 )
							{
								LinkedHashMap< VirtualNode, SdnNode > nodesMapping = ( new Greedy( "Greedy" ) ).run( virtualSubnetwork, sdnNetwork );
					
								if( nodesMapping != null )
								{
									nodesMappings.put( sdnNetwork.getName(), nodesMapping );
								}
								else if( nodesMapping == null )
								{
									System.out.println( "Greedy KO" );
									startingVirtualNetwork.setDenied( true );
									
									break;
								}
							}
							else if( virtualSubnetwork.getVirtualLinks().size() > 0 )
							{
								LinkedHashMap< VirtualNode, SdnNode > nodesMapping = null;
								int linksMapping[][][][] = null;
															
								if( this.getEmbeddingAlgorithm() == null )
								{
									nodesMapping = this.getNodesMappingAlgorithm().run( virtualSubnetwork, sdnNetwork );
									
									if( nodesMapping == null )
									{
										System.out.println( "NodesMappingAlgorithm KO" );
										startingVirtualNetwork.setDenied( true );
										
										break;
									}
								}
								else if( this.getEmbeddingAlgorithm() != null )
								{
									if( this.getEmbeddingAlgorithm().run( virtualSubnetwork, sdnNetwork ) )
									{
										nodesMapping = this.getRoundingAlgorithm().run( virtualSubnetwork, sdnNetwork, this.getEmbeddingAlgorithm().getXValues(), this.getEmbeddingAlgorithm().getFValues() );
	
										if( nodesMapping == null )
										{
											System.out.println( "RoundingAlgorithm KO" );
											startingVirtualNetwork.setDenied( true );
										}
									}
									else
									{
										System.out.println( "EmbeddingAlgorithm KO" );
										startingVirtualNetwork.setDenied( true );
									}
								}
								
								linksMapping = this.getLinksMappingAlgorithm().run( virtualSubnetwork, sdnNetwork, nodesMapping );
								
								if( linksMapping == null )
								{
									System.out.println( "LinksMappingAlgorithm KO" );
									startingVirtualNetwork.setDenied( true );

									break;
								}
								
								nodesMappings.put( sdnNetwork.getName(), nodesMapping );
								linksMappings.put( sdnNetwork.getName(), linksMapping );
							}
					}
				}
			}
			
			if( !startingVirtualNetwork.isDenied() )
			{
				startingVirtualNetwork.setState( Network.ACCEPTED_STATE );
				startingVirtualNetwork.setChange( true );
				
				for( SdnNetwork sdnNetwork: this.getSdnNetworks() )				
					for( VirtualNetwork virtualSubnetwork: virtualSubnetworks )					
						if( virtualSubnetwork.getSdnNetworkName().equals( sdnNetwork.getName() ) )
						{
							virtualSubnetwork.setDenied( false );
							
							if( virtualSubnetwork.getNodes().size() > 0 )
							{
								Iterator< Entry< String, LinkedHashMap< VirtualNode, SdnNode > > > iterator = nodesMappings.entrySet().iterator();
					       
								while( iterator.hasNext() )
								{
									Entry< String, LinkedHashMap< VirtualNode, SdnNode > > virtualNodesMapping = iterator.next();
									
									if( virtualSubnetwork.getSdnNetworkName().equals( virtualNodesMapping.getKey() ) )
									{
										virtualSubnetwork.setVirtualNodesMapping( virtualNodesMapping.getValue() );
										this.nodesAllocating( virtualSubnetwork, sdnNetwork );
										break;
									}
								}
							}
							
							if( virtualSubnetwork.getLinks().size() > 0 )
							{
								Iterator< Entry< String, int[][][][] > > iterator = linksMappings.entrySet().iterator();

								while( iterator.hasNext() )
								{
									Entry< String, int[][][][] > linksMapping = iterator.next();
									
									if( virtualSubnetwork.getSdnNetworkName().equals( linksMapping.getKey() ) )
									{
										virtualSubnetwork.setLinksMapping( linksMapping.getValue() );
										this.linksAllocating( virtualSubnetwork, sdnNetwork );
										break;
									}
								}
							}
				       
							sdnNetwork.setChange( true );
						}
			}
			else
			{
				startingVirtualNetwork.setState( Network.REJECTED_STATE );
				this.setRejectedVirtualNetworksNumber( this.getRejectedVirtualNetworksNumber() + 1 );
			}
		}
		
		return this.getRejectedVirtualNetworksNumber();
	}
	
	public int getSimulationEndingDate()
	{
		int simulationEndingDate = 0;
		
		for( VirtualNetwork virtualNetwork: this.getVirtualNetworks() )
			if( virtualNetwork.getEndingDate() > simulationEndingDate )
				simulationEndingDate = virtualNetwork.getEndingDate();
		
		return simulationEndingDate;
	}
	
	public List< VirtualNetwork > startingVirtualNetworks( int timeInstant )
	{
		List< VirtualNetwork > virtualNetworks = new ArrayList< VirtualNetwork >();
	
		for( VirtualNetwork virtualNetwork: this.getVirtualNetworks() )
			if( virtualNetwork.getStartingDate() == timeInstant )
				virtualNetworks.add( virtualNetwork );
		
		return virtualNetworks;
	}
	private void nodesAllocating( VirtualNetwork virtualNetwork, SdnNetwork sdnNetwork )
	{
		Iterator< SdnNode > iterator1 = sdnNetwork.getSdnNodes().iterator();
		
		while( iterator1.hasNext() )
		{
			SdnNode sdnNode = iterator1.next();
						
			Iterator< Entry< VirtualNode, SdnNode > > iterator2 = virtualNetwork.getNodesMapping().entrySet().iterator();
			
			while( iterator2.hasNext() )
			{
				Entry< VirtualNode, SdnNode > nodesMapping = iterator2.next();
				VirtualNode mappedVirtualNode = nodesMapping.getKey();
				SdnNode mappedSdnNode = nodesMapping.getValue();
				
				if( sdnNode.getNumber() == mappedSdnNode.getNumber() ) 
				{
					sdnNode.setAvailableCPU( sdnNode.getAvailableCPU() - mappedVirtualNode.getCPU() );
			 		sdnNode.setAvailableRAM( sdnNode.getAvailableRAM() - mappedVirtualNode.getRAM() );
			 		
					if( mappedVirtualNode.getType().equals( VirtualNode.SERVER_TYPE ) ) 
						sdnNode.setAvailableStorage( sdnNode.getAvailableStorage() - mappedVirtualNode.getStorage() );
				 	
					sdnNode.setAvailableVirtualNodesNumber( sdnNode.getAvailableVirtualNodesNumber() - 1 );
					
					mappedVirtualNode.setNumber( mappedVirtualNode.getInitialNumber() );
					sdnNode.add( mappedVirtualNode );
					mappedVirtualNode.setSdnNode( sdnNode );
					
					mappedVirtualNode.setCPUprice( sdnNetwork.getCPUprice() );
					mappedVirtualNode.setRAMprice( sdnNetwork.getRAMprice() );
					
					if( mappedVirtualNode.getType().equals( VirtualNode.SERVER_TYPE ) )
						mappedVirtualNode.setStoragePrice( sdnNetwork.getStoragePrice() );
				 }
			}
		}
	}
	private void linksAllocating( VirtualNetwork virtualNetwork, SdnNetwork sdnNetwork )
	{
		int linksMapping[][][][] = virtualNetwork.getLinksMapping();
		
		if( linksMapping != null )
			for( int k = 0; k < virtualNetwork.getVirtualLinks().size(); k++ )
				for( int i = 0; i < sdnNetwork.getNodes().size(); i++ )
					for( int j = 0; j < sdnNetwork.getNodes().size(); j++ )
						for( int backupFlag = 0; backupFlag < 2; backupFlag++ )
							if( linksMapping[ k ][ i ][ j ][ backupFlag ] != 0 )
								for( VirtualLink virtualLink: virtualNetwork.getVirtualLinks() )
									if( virtualLink.getNumber() == k )
										for( SdnLink sdnLink: sdnNetwork.getSdnLinks() )
											if( i == sdnLink.getFirstNode().getNumber() && j == sdnLink.getSecondNode().getNumber() || i == sdnLink.getSecondNode().getNumber() && j == sdnLink.getFirstNode().getNumber() )
											{
												int bw = linksMapping[ k ][ i ][ j ][ backupFlag ];
												
												sdnLink.put( virtualLink, bw, backupFlag );
												virtualLink.put( sdnLink, bw, backupFlag );
												virtualLink.setBWprice( sdnNetwork.getBWprice() );
											}
	}

	public List< VirtualNetwork > completedVirtualNetworks( int timeInstant )
	{
		List< VirtualNetwork > virtualNetworks = new ArrayList< VirtualNetwork >();
		
		for( VirtualNetwork virtualNetwork: this.getVirtualNetworks() )
			if( virtualNetwork.getEndingDate() == timeInstant )
				virtualNetworks.add( virtualNetwork );
		
		return virtualNetworks;
	}
	private void networksReleasing( List< VirtualNetwork > virtualNetworks )
	{
		for( VirtualNetwork virtualNetwork: virtualNetworks )		
			if( virtualNetwork.getState().equals( Network.ACCEPTED_STATE ) )
			{
				virtualNetwork.setChange( true );
				
				if( virtualNetwork.getVirtualSubnetworks() != null )
					for( VirtualNetwork virtualSubnetwork: virtualNetwork.getVirtualSubnetworks() )
						for( SdnNetwork sdnNetwork: this.getSdnNetworks() )
							if( virtualSubnetwork.getSdnNetworkName().equals( sdnNetwork.getName() ) )
								if( !virtualSubnetwork.isDenied() )
								{
									sdnNetwork.setChange( true );
									
									this.nodesReleasing( virtualSubnetwork, sdnNetwork );
									this.linksReleasing( virtualSubnetwork, sdnNetwork );
								}
			}
	}
	private void nodesReleasing( VirtualNetwork virtualNetwork, SdnNetwork sdnNetwork )
	{
		LinkedHashMap< VirtualNode, SdnNode > nodesMapping = virtualNetwork.getNodesMapping();
		Iterator< SdnNode > iterator1 = sdnNetwork.getSdnNodes().iterator();
		
		while( iterator1.hasNext() )
		{
			SdnNode sdnNode = iterator1.next();
			
			if( nodesMapping != null )
			{
				Iterator< Entry< VirtualNode, SdnNode > > iterator2 = nodesMapping.entrySet().iterator();

				while( iterator2.hasNext() )
				{
					Entry< VirtualNode, SdnNode > nodeMapping = iterator2.next();
					VirtualNode mappedVirtualNode = nodeMapping.getKey();
					SdnNode mappedSdnNode = nodeMapping.getValue();
					
					if( sdnNode.getNumber() == mappedSdnNode.getNumber() )
					{
						sdnNode.setAvailableCPU( sdnNode.getAvailableCPU() + mappedVirtualNode.getCPU() );
				 		sdnNode.setAvailableRAM( sdnNode.getAvailableRAM() + mappedVirtualNode.getRAM() );
				 		
						if( mappedVirtualNode instanceof VirtualMachine  )
							sdnNode.setAvailableStorage( sdnNode.getAvailableStorage() + mappedVirtualNode.getStorage() );
						
						sdnNode.setAvailableVirtualNodesNumber( sdnNode.getAvailableVirtualNodesNumber() + 1 );
						
						sdnNode.remove( mappedVirtualNode );
						mappedVirtualNode.setSdnNode( null );
						
						mappedVirtualNode.setCPUprice( 0 );
						mappedVirtualNode.setRAMprice( 0 );
						
						if( mappedVirtualNode.getType().equals( VirtualNode.SERVER_TYPE ) )
							mappedVirtualNode.setStoragePrice( 0 );
					}
				}
			}
		}
	}
	private void linksReleasing( VirtualNetwork virtualNetwork, SdnNetwork sdnNetwork )
	{
		int linksMapping[][][][] = virtualNetwork.getLinksMapping();
		
		if( linksMapping != null )
			for( int k = 0; k < virtualNetwork.getVirtualLinks().size(); k++ )
				for( int i = 0; i < sdnNetwork.getNodes().size(); i++ )
					for( int j = 0; j < sdnNetwork.getNodes().size(); j++ )
						for( int a = 0; a < 2; a++ )
							if( linksMapping[ k ][ i ][ j ][ a ] != 0 )
								for( VirtualLink virtualLink: virtualNetwork.getVirtualLinks() )
									if( virtualLink.getNumber() == k )
										for( SdnLink sdnLink: sdnNetwork.getSdnLinks() )
											if( i == sdnLink.getFirstNode().getNumber() && j == sdnLink.getSecondNode().getNumber() || i == sdnLink.getSecondNode().getNumber() && j == sdnLink.getFirstNode().getNumber() )
											{
												sdnLink.remove( virtualLink );
												virtualLink.remove( sdnLink );
												virtualLink.setBWprice( 0 );
											}
	}
		
}
